跳到主要内容

Go 的时间工具 time 包

time

Golang 包提供的两个时间,"wall clock" 和 "monotonic clock",这个 "wall clock" 可以理解为就是实际的时钟,而 "monotonic clock" 则是程序内部的时钟。

注意:Time 这个包的函数,里面执行的一部分方法都在 src/runtime/time.go 里面,它会在编译的时候编译成 src/runtime/time.go 里的 timer 结构体

如下,通过这个包计算当前程序执行了多久

import (
"fmt"
"time"
)

func main() {
start := time.Now()
time.Sleep(time.Second * 3) // 等待 3s
t := time.Now()
elapsed := t.Sub(start)
fmt.Println(elapsed.String()) // 输出:3.000107755s
}

After 执行超时

可以使用 time 包里面的 After 函数来做个任务超时操作

import (
"fmt"
"time"
)

func handle(int) {
fmt.Println("do something...")
}

func main() {
c := make(chan int)
go func() {
time.Sleep(1 * time.Second)
c <- 1 // 如果不能及时把数据发到管道里面,则会导致下面 timeout
}()

select {
case m := <-c:
handle(m)
case <-time.After(3 * time.Second):
fmt.Println("timed out")
}
}

注意:这个 After 是一次性的,每次 time.After 本质上是创建了一个新的 Timer 结构体,只不过暴露出去的是结构体里的 channel 字段而已。

因此如果在 for{...} 里循环使用了 time.After,将会不断的创建 Timer。如下的使用方法就会带来性能问题:

// 错误的案例 !!!
func main() {
for { // for 里的 time.After 将会不断的创建 Timer 对象
select {
case <-time.After(10 * time.Second):
doSomething()
}
}
}

定时器 timer 和触发器 ticker

两种类型的定时器:ticker 和 timer。

ticker 触发器(持续执行)

ticker 只要定义完成,从此刻开始计时,不需要任何其他的操作,每隔固定时间都会触发。

import (
"fmt"
"time"
)

func main() {
d := time.Duration(time.Second * 2)
t := time.NewTicker(d)
defer t.Stop() // 别忘了清理计时器

for {
<-t.C
fmt.Println("timeout...")
}
}

输出:

timeout...
timeout...
timeout...
timeout...
timeout...
...

循环执行不需要清理的话可以用更简便的 time.Tick() 方法

func main() {
// 创建一个计时器
timeTickerChan := time.Tick(time.Second * 2)
for {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
<-timeTickerChan
}
}

timer 定时器(执行单次)

使用 timer 定时器,超时后需要重置,才能继续触发。

简单的使用

import (
"fmt"
"time"
)

func main() {
d := time.Duration(time.Second * 2)
t := time.NewTimer(d)
defer t.Stop()

for {
<-t.C
fmt.Println("timeout...")
t.Reset(time.Second * 2) // need reset
}
}

输出:

timeout...
timeout...
timeout...
timeout...
timeout...
...

定时器的原理

一开始听到大佬们说定时器使用的是四叉堆,有点纳闷,为啥定时器会需要用到堆,看了源码 才知道,每创建一个 timer 都会被放进 timersBucket(时间桶)里面。

可以在 src/runtime/time.go 里面看到这个 timer 的数据结构,如下

type timer struct {
tb *timersBucket // the bucket the timer lives in
i int // heap index

// Timer wakes up at when, and then at when+period, ... (period > 0 only)
// each time calling f(arg, now) in the timer goroutine, so f must be
// a well-behaved function and not block.
when int64
period int64
f func(interface{}, uintptr)
arg interface{}
seq uintptr
}

这个 timersBucket 是一个全局变量,所以每创建一个 timer 都需要它执行的先后(时间)去执行任务,因此这时就可以使用最小堆这个数据结构了

References

golang 系列:定时器 timer Go 定时器/延时触发器